Micron Document
NexusPi Git Node

Commit bac9ea9ccccb4c0beebdbee12b4a046c862ace25


Parents : a6feed2
Author : James L <jrl290@gmail.com>
Date : 2026-04-26T20:08:48-04:00

Web flasher: add Update vs First Install mode toggle

- Update mode: flashes app-only at 0x10000, preserving NVS/settings
- First Install mode: full merged binary at 0x0, for new/bricked devices
- manifest-v3-full.json, manifest-v4-full.json: merged first-install manifests
- manifest-v3.json, manifest-v4-16mb.json: app-only update manifests

Changes

5 files changed, 134 insertions(+), 14 deletions(-)

M docs/index.html +98 -6

Diff

diff --git a/docs/index.html b/docs/index.html
index 17a15b7..658785e 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -113,6 +113,44 @@
background: rgba(91, 138, 245, 0.22);
}
+ /* ── Mode toggle ────────────────────────────────────── */
+ .mode-toggle {
+ display: flex;
+ gap: 0;
+ background: var(--surface);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ overflow: hidden;
+ margin-bottom: 1.1rem;
+ width: 100%;
+ }
+ .mode-btn {
+ flex: 1;
+ background: none;
+ border: none;
+ color: var(--muted);
+ padding: 0.6rem 0.5rem;
+ font-size: 0.85rem;
+ font-weight: 600;
+ cursor: pointer;
+ transition: background 0.15s, color 0.15s;
+ text-align: center;
+ line-height: 1.3;
+ }
+ .mode-btn:first-child { border-right: 1px solid var(--border); }
+ .mode-btn.active {
+ background: #1c2340;
+ color: var(--text);
+ }
+ .mode-btn .mode-sub {
+ display: block;
+ font-size: 0.72rem;
+ font-weight: 400;
+ color: var(--muted);
+ margin-top: 1px;
+ }
+ .mode-btn.active .mode-sub { color: var(--muted); }
+
/* ── Flash section ──────────────────────────────────── */
.flash-box {
background: var(--surface);
@@ -234,12 +272,28 @@
</section>
- <!-- Step 2: Flash -->
+ <!-- Step 2: Mode + Flash -->
<section class="step">
<div class="step-label">Step 2 &mdash; Connect &amp; install</div>
<div class="flash-box">
- <p>Plug your device into USB, then click the button below. You&rsquo;ll be
- prompted to select the serial port in a browser dialog.</p>
+
+ <!-- Mode toggle -->
+ <div class="mode-toggle">
+ <button class="mode-btn active" id="mode-update">
+ Update firmware
+ <span class="mode-sub">App only &mdash; settings preserved</span>
+ </button>
+ <button class="mode-btn" id="mode-full">
+ First-time install
+ <span class="mode-sub">Full flash &mdash; clears all settings</span>
+ </button>
+ </div>
+
+ <p id="flash-desc">
+ Plug your device into USB, then click the button below. Only the
+ application is flashed &mdash; bootloader, partition table, and all
+ saved settings (LoRa config, identity, WiFi) are left untouched.
+ </p>
<esp-web-install-button id="install-btn" manifest="manifest-v3.json">
</esp-web-install-button>
@@ -247,8 +301,6 @@
<div class="selected-board">
Selected: <span id="selected-name">Heltec V3</span>
</div>
- <p style="margin-top:0.9rem;font-size:0.82rem;color:var(--muted);">&#9888;&nbsp;Flashing clears all device settings (LoRa config, identity, WiFi).
- Reconfigure the node with your host software after flashing.</p>
</div>
</section>
@@ -296,15 +348,55 @@
<script>
const installBtn = document.getElementById('install-btn');
const selectedLbl = document.getElementById('selected-name');
+ const flashDesc = document.getElementById('flash-desc');
+ const modeUpdate = document.getElementById('mode-update');
+ const modeFull = document.getElementById('mode-full');
+
+ // Board → {update manifest, full manifest}
+ const BOARDS = {
+ 'manifest-v3.json': { update: 'manifest-v3.json', full: 'manifest-v3-full.json' },
+ 'manifest-v4-16mb.json': { update: 'manifest-v4-16mb.json', full: 'manifest-v4-full.json' },
+ };
+
+ let currentBoard = 'manifest-v3.json';
+ let currentIsUpdate = true;
+
+ const UPDATE_DESC = "Plug your device into USB, then click the button below. Only the " +
+ "application is flashed \u2014 bootloader, partition table, and all " +
+ "saved settings (LoRa config, identity, WiFi) are left untouched.";
+ const FULL_DESC = "\u26A0\uFE0F First-time install: the full firmware image is written from offset 0x0. " +
+ "This is required for new devices or to recover a bricked node. " +
+ "All existing settings will be erased.";
+
+ function updateButton() {
+ const manifests = BOARDS[currentBoard];
+ installBtn.setAttribute('manifest', currentIsUpdate ? manifests.update : manifests.full);
+ flashDesc.textContent = currentIsUpdate ? UPDATE_DESC : FULL_DESC;
+ }
document.querySelectorAll('.board-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.board-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
- installBtn.setAttribute('manifest', btn.dataset.manifest);
+ currentBoard = btn.dataset.manifest;
selectedLbl.textContent = btn.dataset.name;
+ updateButton();
});
});
+
+ modeUpdate.addEventListener('click', () => {
+ modeUpdate.classList.add('active');
+ modeFull.classList.remove('active');
+ currentIsUpdate = true;
+ updateButton();
+ });
+
+ modeFull.addEventListener('click', () => {
+ modeFull.classList.add('active');
+ modeUpdate.classList.remove('active');
+ currentIsUpdate = false;
+ updateButton();
+ });
</script>
</body>
</html>

diff --git a/docs/manifest-v3-full.json b/docs/manifest-v3-full.json
new file mode 100644
index 0000000..16635a5
--- /dev/null
+++ b/docs/manifest-v3-full.json
@@ -0,0 +1,15 @@
+{
+ "name": "RTNode - Heltec V3 (First Install)",
+ "new_install_prompt_erase": true,
+ "builds": [
+ {
+ "chipFamily": "ESP32-S3",
+ "parts": [
+ {
+ "path": "https://github.com/jrl290/RTNode-HeltecV4/releases/latest/download/rnode_firmware_heltec32v3_merged.bin",
+ "offset": 0
+ }
+ ]
+ }
+ ]
+}

diff --git a/docs/manifest-v3.json b/docs/manifest-v3.json
index 6c888bf..f84b1d5 100644
--- a/docs/manifest-v3.json
+++ b/docs/manifest-v3.json
@@ -1,13 +1,12 @@
{
- "name": "RTNode - Heltec V3",
- "new_install_prompt_erase": true,
+ "name": "RTNode - Heltec V3 (Update)",
"builds": [
{
"chipFamily": "ESP32-S3",
"parts": [
{
- "path": "https://github.com/jrl290/RTNode-HeltecV4/releases/latest/download/rnode_firmware_heltec32v3_merged.bin",
- "offset": 0
+ "path": "https://github.com/jrl290/RTNode-HeltecV4/releases/latest/download/rnode_firmware_heltec32v3.bin",
+ "offset": 65536
}
]
}

diff --git a/docs/manifest-v4-16mb.json b/docs/manifest-v4-16mb.json
index a8680df..143fc03 100644
--- a/docs/manifest-v4-16mb.json
+++ b/docs/manifest-v4-16mb.json
@@ -1,13 +1,12 @@
{
- "name": "RTNode - Heltec V4 (16 MB)",
- "new_install_prompt_erase": true,
+ "name": "RTNode - Heltec V4 (Update)",
"builds": [
{
"chipFamily": "ESP32-S3",
"parts": [
{
- "path": "https://github.com/jrl290/RTNode-HeltecV4/releases/latest/download/rnode_firmware_heltec32v4_boundary_16mb_merged.bin",
- "offset": 0
+ "path": "https://github.com/jrl290/RTNode-HeltecV4/releases/latest/download/rnode_firmware_heltec32v4_boundary_16mb.bin",
+ "offset": 65536
}
]
}

diff --git a/docs/manifest-v4-full.json b/docs/manifest-v4-full.json
new file mode 100644
index 0000000..13d17d2
--- /dev/null
+++ b/docs/manifest-v4-full.json
@@ -0,0 +1,15 @@
+{
+ "name": "RTNode - Heltec V4 (First Install)",
+ "new_install_prompt_erase": true,
+ "builds": [
+ {
+ "chipFamily": "ESP32-S3",
+ "parts": [
+ {
+ "path": "https://github.com/jrl290/RTNode-HeltecV4/releases/latest/download/rnode_firmware_heltec32v4_boundary_16mb_merged.bin",
+ "offset": 0
+ }
+ ]
+ }
+ ]
+}

Served by rngit 1.4.2 - Generated in 0.12s